// Copyright 2022 jeff.li. and/or its affiliates.

#include <chrono>
#include <iomanip>
#include <iostream>
#include <map>
#include <random>
#include <string>
#include <vector>

#include <turbo/files/filesystem.h>
#include <gtest/gtest.h>

#include "test_util.h"

namespace tbir::runtime {

    TEST(filesystem, du) {

        using namespace ::tbir::test;

        turbo::filesystem::path dir{"."};

        uint64_t totalSize = 0;
        int totalDirs = 0;
        int totalFiles = 0;
        int maxDepth = 0;

        try {
            auto rdi = turbo::filesystem::recursive_directory_iterator(dir);
            for (auto de : rdi) {
                if (rdi.depth() > maxDepth) {
                    maxDepth = rdi.depth();
                }
                if (de.is_regular_file()) {
                    totalSize += de.file_size();
                    ++totalFiles;
                } else if (de.is_directory()) {
                    ++totalDirs;
                }
            }
        } catch (turbo::filesystem::filesystem_error fe) {
            std::cerr << "Error: " << fe.what() << std::endl;
            exit(1);
        }
        std::cout << totalSize << " bytes in " << totalFiles << " files and " << totalDirs
                  << " directories, maximum depth: " << maxDepth << std::endl;
    }

    TEST(filesystem, dir) {

        using namespace ::tbir::test;

        turbo::filesystem::path dir{"."};
        for (auto de : turbo::filesystem::directory_iterator(dir)) {
            auto ft = to_time_t(de.last_write_time());
            auto ftm = *std::localtime(&ft);
            std::cout << (de.is_directory() ? "d" : "-") << perm_to_str(de.symlink_status().permissions())
                      << "  " << std::setw(8) << (de.is_directory() ? "-" : std::to_string(de.file_size()))
                      << "  " << std::put_time(&ftm, "%Y-%m-%d %H:%M:%S") << "  "
                      << de.path().filename().string() << std::endl;
        }
    }

    TEST(filesystem, copy_R_case1) {
        // source is a regular dir and subdir has symlink.

        using namespace ::tbir::test;
        TemporaryDirectory t(TempOpt::change_path);
        std::cout << t.path() << std::endl;

        std::error_code ec;
        turbo::filesystem::create_directory("dir1");
        generateFile("dir1/file1");
        generateFile("dir1/file2");
        turbo::filesystem::create_directory("dir1/dir2");
        generateFile("dir1/dir2/file3");

        const auto copy_options = turbo::filesystem::copy_options::recursive;

        turbo::filesystem::copy("dir1", "dir3", copy_options);
        ASSERT_TRUE(!ec);
        ASSERT_TRUE(turbo::filesystem::exists("dir3/file1"));
        ASSERT_TRUE(turbo::filesystem::exists("dir3/file2"));
        ASSERT_TRUE(turbo::filesystem::is_regular_file("dir3/file2"));
        ASSERT_TRUE(turbo::filesystem::exists("dir3/dir2/file3"));
        ASSERT_TRUE(turbo::filesystem::is_regular_file("dir3/dir2/file3"));
    }

    TEST(filesystem, copy_R_case2) {
        // source is a symlink and subdir has symlink.
        using namespace ::tbir::test;
        TemporaryDirectory t(TempOpt::change_path);
        std::cout << t.path() << std::endl;

        turbo::filesystem::create_directory("test");
        generateFile("test/abc");

        std::error_code ec;
        turbo::filesystem::create_directory("dir1");
        generateFile("dir1/file1");
        generateFile("dir1/file2");
        turbo::filesystem::create_directory("dir1/dir2");
        generateFile("dir1/dir2/file3");
        turbo::filesystem::create_symlink("dir1", "dir1_sym");
        ASSERT_TRUE(turbo::filesystem::exists("dir1_sym"));
        ASSERT_TRUE(turbo::filesystem::is_symlink("dir1_sym"));

        turbo::filesystem::create_symlink("../../test/abc", "dir1/dir2/abc_sym");
        ASSERT_TRUE(turbo::filesystem::exists("dir1/dir2/abc_sym"));
        ASSERT_TRUE(turbo::filesystem::is_symlink("dir1/dir2/abc_sym"));

        const auto copy_options = turbo::filesystem::copy_options::recursive;

        turbo::filesystem::copy("dir1_sym", "dir3", copy_options);
        ASSERT_TRUE(!ec);
        ASSERT_TRUE(turbo::filesystem::exists("dir3/file1"));
        ASSERT_TRUE(turbo::filesystem::exists("dir3/file2"));
        ASSERT_TRUE(turbo::filesystem::exists("dir3/dir2/file3"));
        ASSERT_TRUE(turbo::filesystem::is_regular_file("dir3/dir2/file3"));
        ASSERT_TRUE(turbo::filesystem::is_regular_file("dir3/file2"));
        ASSERT_TRUE(turbo::filesystem::is_regular_file("dir3/file1"));
        ASSERT_TRUE(turbo::filesystem::exists("dir3/dir2/file3"));
        ASSERT_TRUE(turbo::filesystem::is_regular_file("dir3/dir2/file3"));
    }

    TEST(filesystem, copy_rd_case1) {
        using namespace ::tbir::test;
        TemporaryDirectory t(TempOpt::change_path);
        std::cout << t.path() << std::endl;

        std::error_code ec;
        turbo::filesystem::create_directory("dir1");
        generateFile("dir1/file1");
        generateFile("dir1/file2");
        turbo::filesystem::create_directory("dir1/dir2");
        generateFile("dir1/dir2/file3");
        turbo::filesystem::create_symlink("dir1", "dir1_sym");
        // check src symlink
        ASSERT_TRUE(turbo::filesystem::exists("dir1_sym"));
        ASSERT_TRUE(turbo::filesystem::is_symlink("dir1_sym"));
        const auto copy_options = turbo::filesystem::copy_options::recursive | turbo::filesystem::copy_options::copy_symlinks;
        turbo::filesystem::copy("dir1_sym", "dir3", copy_options);
        ASSERT_TRUE(!ec);
        ASSERT_TRUE(turbo::filesystem::exists("dir3/file1"));
        ASSERT_TRUE(turbo::filesystem::is_symlink("dir3"));
        ASSERT_TRUE(turbo::filesystem::exists("dir3/file2"));
        ASSERT_TRUE(turbo::filesystem::is_regular_file("dir3/file2"));
        ASSERT_TRUE(turbo::filesystem::exists("dir3/dir2/file3"));
        ASSERT_TRUE(turbo::filesystem::is_regular_file("dir3/dir2/file3"));;
    }

    TEST(filesystem, copy_rd_case2) {
        using namespace ::tbir::test;
        TemporaryDirectory t(TempOpt::change_path);
        std::cout << t.path() << std::endl;

        turbo::filesystem::create_directory("test");
        generateFile("test/abc");

        std::error_code ec;
        turbo::filesystem::create_directory("dir1");
        generateFile("dir1/file1");
        generateFile("dir1/file2");
        turbo::filesystem::create_directory("dir1/dir2");
        generateFile("dir1/dir2/file3");

        turbo::filesystem::create_symlink("../../test/abc", "dir1/dir2/abc_sym");
        ASSERT_TRUE(turbo::filesystem::exists("dir1/dir2/abc_sym"));
        ASSERT_TRUE(turbo::filesystem::is_symlink("dir1/dir2/abc_sym"));

        const auto copy_options = turbo::filesystem::copy_options::recursive | turbo::filesystem::copy_options::copy_symlinks;
        turbo::filesystem::copy("dir1", "dir3", copy_options);
        ASSERT_TRUE(!ec);
        ASSERT_TRUE(turbo::filesystem::exists("dir3/file1"));
        ASSERT_TRUE(turbo::filesystem::is_directory("dir3"));
        ASSERT_TRUE(turbo::filesystem::exists("dir3/file2"));
        ASSERT_TRUE(turbo::filesystem::is_regular_file("dir3/file2"));
        ASSERT_TRUE(turbo::filesystem::exists("dir3/dir2/file3"));
        ASSERT_TRUE(turbo::filesystem::is_regular_file("dir3/dir2/file3"));;
    }

    TEST(filesystem, create_directories) {
        using namespace ::tbir::test;
        TemporaryDirectory t(TempOpt::change_path);
        std::cout << t.path() << std::endl;

        std::error_code ec;
        turbo::filesystem::create_directories("dir1/dir2/dir3/abc/", ec);
        ASSERT_TRUE(!ec);
        generateFile("dir1/file1");
        generateFile("dir1/file2");
        generateFile("dir1/dir2/file3");

        ASSERT_TRUE(turbo::filesystem::exists("dir1/dir2/dir3/abc"));
        ASSERT_TRUE(turbo::filesystem::is_directory("dir1/dir2/dir3"));
        ASSERT_TRUE(turbo::filesystem::exists("dir1/dir2/dir3/abc"));
    }

}  // namespace tbir::runtime
